/***** Example of Isometric Projection
 *
 * This example shows how to project a large amount of 3D points to the screen
 * without any perpoint trig functions.  Once you have these points in screen-
 * space you can blit sprites, draw polygons, or whatever you like.
 */

/* I prefer floats to Allegro's fixed point numbers so we need math.h for it's
 * sin() and cos() functions.
 */
#include <math.h>
#include <allegro.h>

int main(void)
{
   /* angles to rotate all the vectors with, they're updated with key input
    * note that the angles are in radians
    */
   float rotation = 1.0;
   float elevation = 1.0;
   float spin = 0.0;
   float zoom = 20.0;
   
   /* rotated vectors to transform all the points*/
   float right_x, right_y;
   float front_x, front_y;
   float up_x, up_y;
   
   /* temporary 3D point that will be projected by the above vectors*/
   float x, y, z;
   
   /* final screen coordinates of projected point */
   float screen_x, screen_y;
   
   /* initialize Allegro and setup a double buffer */   
   BITMAP *buffer;

   if (allegro_init() != 0)
      return 1;
   install_timer();
   install_keyboard();

   if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0) != 0) 
    {
     if (set_gfx_mode(GFX_SAFE, 320, 200, 0, 0) != 0)
      {
	   set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
	   allegro_message("Unable to set any graphic mode\n%s\n", allegro_error);
	   return 1;
      }
    }
   buffer = create_bitmap(SCREEN_W, SCREEN_H);

   /* main loop */    
   while (!key[KEY_ESC])
    {
     clear_bitmap(buffer);
          
     /* user input to control the various rotations */
     if (key[KEY_4_PAD]) rotation += 0.01;
     if (key[KEY_6_PAD]) rotation -= 0.01;     
     
     if (key[KEY_8_PAD]) elevation += 0.01;
     if (key[KEY_2_PAD]) elevation -= 0.01;     

     if (key[KEY_9_PAD]) zoom *= 0.99;
     if (key[KEY_3_PAD]) zoom *= 1.01;     

     if (key[KEY_7_PAD]) spin += 0.01;
     if (key[KEY_1_PAD]) spin -= 0.01;     

     /* We need to find out where the points (1,0,0), (0,1,0), and (0,0,1) are
      * projected onto the screen.  With these we can calculate where any 3D
      * point is on the screen.
      */
     right_x = (cos(spin) * cos(rotation) - sin(spin) * sin(rotation) * cos(elevation)) * zoom;
     right_y = (sin(spin) * cos(rotation) + cos(spin) * sin(rotation) * cos(elevation)) * zoom;
     
     front_x = (cos(spin) * sin(rotation) - sin(spin) * -cos(rotation) * cos(elevation)) * zoom;
     front_y = (sin(spin) * sin(rotation) + cos(spin) * -cos(rotation) * cos(elevation)) * zoom;
     
     up_x = -sin(spin) * sin(elevation) * zoom;
     up_y = cos(spin) * sin(elevation) * zoom;
     
     /* Now we generate some 3D points and project them on the screen using the
      * right, front, and up vectors.
      */     
     for (x = -5.0; x <= 5.0; x += 0.1)
      for (y = -5.0; y <= 5.0; y += 0.1)
       {
        /* These trig functions simply generate a height map and have nothing
         * to do with projection.  In a real game you would get the points
         * directly from memory instead of generating them every frame.
         */
        z = sin(x) - cos(y);
        
        /* Projection onto screen */
        screen_x = x * right_x + y * front_x + z * up_x;
        screen_y = x * right_y + y * front_y + z * up_y;

        /* Draw the point with 0,0 in the middle of the screen instead of the
         * upper left corner
         */
        putpixel(buffer, 
                 (int)floor(screen_x + SCREEN_W * 0.5), 
                 (int)floor(screen_y + SCREEN_H * 0.5),
                 1);
       }
      
     blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
     yield_timeslice();
    }
    
   destroy_bitmap(buffer);
   return 0;
}

END_OF_MAIN();

